using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Ccr.Core;
using Microsoft.Ccr.Core.Arbiters;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace Ccr
{
  public abstract class Actor : IDisposable
    {
        private readonly DispatcherQueue m_queue;
        private PortSet m_portSet;
        private List<HandlerEntry> m_handlers;
        private List<TimerEntry> m_timers;
        private bool? m_isDedicated = null;
            
        protected Actor()
        {
            if (this.IsDedicated)
                m_queue = DedicatedDispatcherFactory.Create(this.DedicatedThreadCount);
            else
                m_queue = Factory.Queue;                
  
            this.InitPorts();
            this.InitHandlers();

            if (this.IsAutorun)
                this.Start();
        }

        protected bool IsDedicated
        {
            get
            {
                if (!m_isDedicated.HasValue)
                    m_isDedicated = this.GetType().GetCustomAttributes(typeof(DedicatedAttribute), true).Any();

                return (bool)m_isDedicated;
            }
        }
        protected bool IsAutorun
        {
            get
            {
                return this.GetType().GetCustomAttributes(typeof(AutorunAttribute), false).Any();                    
            }
        }
        
        private void InitPorts()
        {
            List<Type> types = new List<Type>();

            MethodInfo[] methodInfo = this.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
            for (int i = 0; i < methodInfo.Length; i++)
            {
                HandlerAttribute ccrHandler = (HandlerAttribute)Attribute.GetCustomAttribute(methodInfo[i], typeof(HandlerAttribute));
                if (ccrHandler != null)
                    types.Add(methodInfo[i].GetParameters()[0].ParameterType);
            }

            m_portSet = new PortSet(types.ToArray());   
        }
        private void InitHandlers()
        {
            m_handlers = new List<HandlerEntry>();
            m_timers = new List<TimerEntry>();

            MethodInfo[] methodInfo = this.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
            for (int i = 0; i < methodInfo.Length; i++)
            {
                HandlerAttribute ccrHandler = (HandlerAttribute)Attribute.GetCustomAttribute(methodInfo[i], typeof(HandlerAttribute));                
                if (ccrHandler != null)
                {
                    Type msgType = methodInfo[i].GetParameters()[0].ParameterType;

                    string methodName = string.Empty;
                    Type delegateType = null;

                    if (methodInfo[i].ReturnType == typeof(void))
                    {
                        methodName = "ReceiveFromPortSet";
                        delegateType = typeof(Handler<>);
                    }
                    else
                    if (methodInfo[i].ReturnType == typeof(IEnumerator<ITask>))
                    {
                        methodName = "ReceiveWithIteratorFromPortSet";
                        delegateType = typeof(IteratorHandler<>);
                    }

                    MethodInfo miArbiter = typeof(Arbiter).GetMethods().First(mi => mi.Name == methodName && mi.GetParameters().Count() == 3).MakeGenericMethod(msgType);
                    Delegate handler = Delegate.CreateDelegate(delegateType.MakeGenericType(msgType), this, methodInfo[i].Name);                             
                    ReceiverTask taskReceive = (ReceiverTask)miArbiter.Invoke(null, new object[] { ccrHandler.IsPersist, m_portSet, handler });

                    m_handlers.Add(new HandlerEntry(ccrHandler.Behavior, taskReceive));
                
                    if (ccrHandler.Delay > 0)
                        this.Shedule(msgType, ccrHandler.Delay);         
                }
            }            
        }
        private void Shedule(Type msgType, int ms)
        {
            Port<DateTime> port = new Port<DateTime>();

            object msg = Activator.CreateInstance(msgType);

            Receiver<DateTime> task = Arbiter.Receive(true, port, t =>
            {
                Arbiter.Activate(m_queue, 
                    Arbiter.Receive(false, TimeoutPort(ms), t2 =>
                    {
                        port.Post(DateTime.Now);
                        m_portSet.PostUnknownType(msg);
                    } ));             
            });

            m_timers.Add(new TimerEntry(port, task));
        }
        private ReceiverTask[] GetTasks(Behavior behavior)
        {
            return m_handlers.Where(entry => entry.Behavior == behavior).Select(HandlerEntry.TaskSelector).ToArray(); 
        }

        protected int DedicatedThreadCount
        {
            get
            {
                foreach (Attribute attribute in this.GetType().GetCustomAttributes(true))
                {
                    DedicatedAttribute attr = attribute as DedicatedAttribute;
                    if (attr != null)
                        return attr.ThreadCount;
                }

                throw new InvalidOperationException();
            }
        }
        protected Port<DateTime> TimeoutPort(int ms)
        {
            TimeSpan ts = new TimeSpan(0, 0, 0, 0, ms);
            Port<DateTime> timerPort = new Port<DateTime>();
            this.m_queue.EnqueueTimer(ts, timerPort);
            return timerPort;
        }
        protected void Shedule<T>(int ms)
        {
            this.Shedule(typeof(T), ms);
        }        
        protected void Start()
        {
            this.OnStart();
                       
            Arbiter.Activate(m_queue,
                Arbiter.Interleave(
                    new TeardownReceiverGroup(this.GetTasks(Behavior.Teardown)),
                    new ExclusiveReceiverGroup(this.GetTasks(Behavior.Exclusive)),
                    new ConcurrentReceiverGroup(this.GetTasks(Behavior.Concurrent))
                ));
            
            m_timers.ForEach(timerEntry => timerEntry.Port.Post(DateTime.Now));
            Arbiter.Activate(m_queue, m_timers.Select(TimerEntry.TaskSelector).ToArray());      
        }

        protected virtual void OnStart()
        {

        }

        protected virtual void OnTeardown()
        {            
        }
                     
        [Handler(Behavior.Teardown)] 
        protected void OnTeardown(Message.Teardown msg)
        {
            this.OnTeardown();

            m_handlers.Clear();
            m_timers.Clear();

            if (this.IsDedicated)
                m_queue.Dispatcher.Dispose();
        }

        public void Post<T>(T msg)
        {
            m_portSet.TryPostUnknownType(msg);
        }
        public void Post<T>() where T: new()
        {
            this.Post<T>(Activator.CreateInstance<T>());
        }

        public void Dispose()
        {
            this.Post<Message.Teardown>();
        }
    }
}
